use crate::db_emu::KVdbEmu;
use crate::proof_of_work::ProofOfWork;
use crate::transaction::{TXOutput, Transaction};
use crate::util::bytes_to_hex_string;
use p256::SecretKey;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::cell::RefCell;
use std::collections::HashMap;
use std::time::SystemTime;
use crate::merkle_tree::MerkleTree;
use crate::utxo_set::TXOutputIndexWrap;

#[derive(Serialize, Deserialize)]
#[derive(Clone)]
pub struct Block {
    pub timestamp: i64,  // 当前时间戳, 区块被创建那一时间点
    pub transactions: Vec<Transaction>, // 填充进区块的信息
    pub prev_block_hash: Vec<u8>, // 前一区块的哈希
    pub nonce: i64, // 工作量
    pub hash: Vec<u8>, // 当前区块的哈希
}

pub struct Blockchain {
    pub tip: Vec<u8>,
    // pub blocks: Vec<RefCell<Block>>,
    pub db: RefCell<KVdbEmu>,
}

pub struct BlockchainIterator {
    current_hash: Vec<u8>,
    db: RefCell<KVdbEmu>,
}

impl Block {
    fn set_hash(&mut self) {
        let mut hasher = Sha256::new();
        hasher.update(self.timestamp.to_be_bytes());
        hasher.update(&self.hash_transactions());
        hasher.update(&self.prev_block_hash);
        self.hash = hasher.finalize().to_vec();
    }

    fn new_block(transactions: Vec<Transaction>, prev_block_hash: &Vec<u8>) -> RefCell<Block> {
        let timestamp = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
            Ok(n) => n.as_secs() as i64,
            Err(_) => panic!("System Time is before 1970"),
        };

        let mut nb = Block {
            timestamp: timestamp,
            transactions: transactions,
            prev_block_hash: prev_block_hash.to_vec(),
            nonce: 0,
            hash: vec![],
        };
        nb.set_hash();
        let rcbn = RefCell::new(nb);

        let pow = ProofOfWork::new_proof_of_work(RefCell::clone(&rcbn));
        let (nonce, hash) = pow.run();
        rcbn.borrow_mut().nonce = nonce;
        rcbn.borrow_mut().hash = hash;

        assert_ne!(*prev_block_hash, *rcbn.borrow_mut().hash);

        rcbn
    }

    pub fn serialize(&self) -> Vec<u8> {
        let result = serde_json::to_string(&self).unwrap();
        result.into_bytes().to_vec()
    }

    pub fn deserialize_block(d: &Vec<u8>) -> Block {
        let block = serde_json::from_slice(d).unwrap();
        block
    }

    pub fn hash_transactions(&self) -> Vec<u8> {
        let mut transactions:Vec<Vec<u8>> = vec![];

        for tx in &self.transactions {
            transactions.push(serde_json::to_string(tx).unwrap().into_bytes());
        }

        let m_tree = MerkleTree::new_merkle_tree(transactions);
        m_tree.root.data.clone()
    }
}

impl Blockchain {
    pub fn mine_block(&mut self, transactions: Vec<Transaction>) -> RefCell<Block> {
        let last_hash = self.db.borrow().get(&"l".as_bytes().to_vec()).unwrap().to_vec();

        for tx in &transactions {
            if self.verify_transaction(&tx) != true {
                panic!("ERROR: Invalid transaction")
            }
        }

        let nb = Block::new_block(transactions, &last_hash);
        self.db.borrow_mut().put(&nb.borrow().hash, &nb.borrow().serialize());
        self.db.borrow_mut().put(&"l".as_bytes().to_vec(), &nb.borrow().hash);
        self.tip = last_hash;
        nb
    }

    pub fn new_genesis_block(coinbase: Transaction) -> RefCell<Block> {
        Block::new_block(vec![coinbase], &vec![])
    }

    pub fn new_blockchain(address: &Vec<u8>) -> Blockchain {
        let tip: Vec<u8>;
        let mut db = KVdbEmu::new();

        let b = db.get(&"l".as_bytes().to_vec());

        match b {
            None => {
                let pub_key_hash = address[1..address.len() - 4].to_vec();
                let cbtx = Transaction::new_coinbase_tx(address, "genesisCoinbaseData");
                let genesis = Self::new_genesis_block(cbtx);
                db.put(&genesis.borrow().hash, &genesis.borrow().serialize());
                db.put(&"l".as_bytes().to_vec(), &genesis.borrow().hash);
                tip = genesis.borrow().hash.clone();
            }
            Some(bv) => {
                tip = bv;
            }
        }

        // Blockchain { tip, blocks: vec![Self::new_genesis_block()], db: RefCell::new(db) }
        Blockchain { tip, db: RefCell::new(db) }
    }

    pub fn iterator(&self) -> BlockchainIterator {
        BlockchainIterator { current_hash: self.tip.clone(), db: RefCell::clone(&self.db) }
    }

    pub fn find_unspent_transactions(&self, pub_key_hash: &Vec<u8>) -> Vec<Transaction> {
        let mut unspent_txs = vec![];
        let mut spent_txos: HashMap<Vec<u8>, Vec<usize>> = HashMap::new();

        let mut bci = self.iterator();

        loop {
            let block = bci.next();

            for tx in &block.transactions {
                let txid = tx.id.clone();

                'Outputs: for out_idx in 0..tx.vout.len() {
                    let out = &tx.vout[out_idx];
                    if spent_txos.get(&txid).is_some() {
                        for spent_out in spent_txos.get(&txid).unwrap() {
                            if *spent_out == out_idx {
                                continue 'Outputs;
                            }
                        }
                    }

                    if out.is_locked_with_key(pub_key_hash) {
                        unspent_txs.push(tx.clone());
                    }
                }

                if !tx.is_coninbase() {
                    for tin in &tx.vin {
                        if tin.uses_key(pub_key_hash) {
                            let spent_arr_opt = spent_txos.get_mut(&tin.txid);
                            match spent_arr_opt {
                                None => {
                                    spent_txos.insert(tin.txid.clone(), vec![tin.vout]);
                                }
                                Some(spent_arr) => {
                                    spent_arr.push(tin.vout);
                                }
                            }
                        }
                    }
                }
            }

            if block.prev_block_hash.len() == 0 {
                break;
            }
        }

        unspent_txs
    }

    pub fn find_all_utxo(&self) -> HashMap<Vec<u8>, Vec<TXOutputIndexWrap>> {
        let mut utxos: HashMap<Vec<u8>, Vec<TXOutputIndexWrap>> = HashMap::new();
        let mut spent_txos: HashMap<Vec<u8>, Vec<usize>> = HashMap::new();
        let mut bci = self.iterator();

        loop {
            let block = bci.next();

            for tx in block.transactions {
                'Outputs: for out_idx in 0..tx.vout.len() {
                    if spent_txos.get(&tx.id).is_some() {
                        for spent_out_idx in spent_txos.get(&tx.id).unwrap() {
                            if *spent_out_idx == out_idx {
                                continue 'Outputs;
                            }
                        }
                    }

                    let outs_opt = utxos.get_mut(&tx.id);
                    let uout_wrap = TXOutputIndexWrap {
                        vout_idx: out_idx,
                        out: tx.vout[out_idx].clone(),
                    };
                    match outs_opt {
                        None => {
                            let outs = vec![uout_wrap];
                            utxos.insert(tx.id.clone(), outs);
                        }
                        Some(outs) => {
                            outs.push(uout_wrap);
                        }
                    }
                }

                if !tx.is_coninbase() {
                    for tin in &tx.vin {
                        let spent_outs_opt = spent_txos.get_mut(&tin.txid);
                        match spent_outs_opt {
                            None => {
                                let outs = vec![tin.vout];
                                spent_txos.insert(tin.txid.clone(), outs);
                            }
                            Some(outs) => {
                                outs.push(tin.vout);
                            }
                        }
                    }
                }
            }

            if block.prev_block_hash.len() == 0 {
                break;
            }
        }

        utxos
    }

    pub fn find_transaction(&self, id: &Vec<u8>) -> Transaction {
        let mut bci = self.iterator();

        loop {
            let block = bci.next();
            for tx in &block.transactions {
                if &tx.id == id {
                    return tx.clone();
                }
            }

            if block.prev_block_hash.len() == 0 {
                break;
            }
        }

        panic!("Transaction not found");
    }

    pub fn sign_transaction(&self, tx: &mut Transaction, priv_key: &SecretKey) {
        let mut prev_txs: HashMap<Vec<u8>, Transaction> = HashMap::new();

        for vin in &tx.vin {
            let prev_tx = self.find_transaction(&vin.txid);
            prev_txs.insert(prev_tx.id.clone(), prev_tx);
        }

        tx.sign(priv_key, &prev_txs)
    }

    pub fn verify_transaction(&self, tx: &Transaction) -> bool {
        let mut prev_txs: HashMap<Vec<u8>, Transaction> = HashMap::new();

        for vin in &tx.vin {
            let prev_tx = self.find_transaction(&vin.txid);
            prev_txs.insert(prev_tx.id.clone(), prev_tx);
        }
        tx.verify(&prev_txs)
    }
}

impl BlockchainIterator {
    pub fn next(&mut self) -> Block {
        let b = self.db.borrow().get(&self.current_hash).unwrap();
        let block = Block::deserialize_block(&b.to_vec());
        self.current_hash = block.prev_block_hash.clone();

        block
    }
}